package org.liang.transfer.sftp;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.liang.common.utils.io.FileUtil;
import org.liang.transfer.exception.UncorrectedRemotePathException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.SftpException;

/**
 * SFTP 下载器.
 * 
 * <pre>
 * 整合SFTP下载实现多有代码。
 * 此类仅向{@link SFTP}开放访问
 * </pre>
 * 
 * @author L.Yang
 * @version v1.0 2016年4月11日 下午3:46:20
 */
class SFTPDownloader {

    private static final Logger LOGGER = LoggerFactory.getLogger(SFTPDownloader.class);

    private static final String PARETN_DIR = "..";

    private ChannelSftp sftp = null;

    SFTPDownloader(ChannelSftp sftp) {
        super();
        this.sftp = sftp;
    }

    /**
     * 下载远程服务器上的文件，并存储到本地.
     * 
     * <pre>
     * remote 如果是文件夹则将对文件夹中的文件进行filter过滤，将符合filter的文件下载；
     * 如果local 是文件名则使用locl 如果local 是路径则使用服务器文件名；
     * 过滤规则： 远程文件命中包含filter字符串。
     * tips： 文件夹必须包含结束字符串，否则认定为文件 。在文件目录中过滤符合名称的文件进行下载
     * </pre>
     * 
     * @param remote
     *            远程FTP服务器文件的完整路径
     * @param local
     *            本地存储文件的完整路径
     * @param filter
     *            过滤内容
     * @throws UncorrectedRemotePathException
     *             当远程路径不存在是抛出此异常
     */
    void download(String remote, String local, String filter) {
        try {
            changeWorkingDirectory(remote);
            String remotename = FilenameUtils.getName(remote);
            if (StringUtils.isNotEmpty(remotename)) {
                remote = FilenameUtils.getFullPath(remote);
                filter = remotename;
            }
            List<RemoteFile> remotefiles = listFiles(remote, filter);
            for (RemoteFile remotefile : remotefiles) {
                recursivedownload(local, remotefile, filter);
            }
        } catch (SftpException e) {
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
        }
    }

    private static final int EXCEPTION_NO_SUCH_FILE = 2;

    /**
     * 变更工作路径.
     * 
     * <pre>
     * 如果路径不存在则抛出 {@link UncorrectedRemotePathException}
     * </pre>
     * 
     * @param remote
     *            远程路径
     * @throws SftpException
     *             - ChannelSftp 抛出的异常
     * @see {@link ChannelSftp#cd(String)}
     */
    private void changeWorkingDirectory(String remote) throws SftpException {
        String workingDirectory = FilenameUtils.getFullPath(remote);
        try {
            sftp.cd(workingDirectory);
        } catch (SftpException e) {
            if (e.id == EXCEPTION_NO_SUCH_FILE) {
                throw new UncorrectedRemotePathException(workingDirectory);
            } else {
                throw e;
            }

        }
        LOGGER.trace("进入工作目录 : {}", new Object[] {workingDirectory});
    }

    /**
     * 递归下载.
     * 
     * <pre>
     * 根据名称判断是否是文件夹：
     *  1. 如果是文件夹则递归下载；
     *  2. 如果不是则进行过滤下载。
     * </pre>
     * 
     * @param local
     *            本地路径
     * @param file
     *            ftp文件
     * @param filter
     *            过滤字符串
     * @throws SftpException
     *             - ChannelSftp 抛出的异常
     * @see {@link ChannelSftp#get(String, int)}
     */
    private void recursivedownload(String local, RemoteFile remotefile, String filter)
                    throws SftpException {
        if (remotefile.isDirectory()) {
            String filename = remotefile.getName();
            sftp.cd(filename);
            LOGGER.trace("进入子文件夹： {}", filename);

            String newloaclpath = generateLocalPath(local, filename);

            List<RemoteFile> remotefiles = listFiles(remotefile.getAbsolutePath(), filter);
            for (RemoteFile ftpfile : remotefiles) {
                recursivedownload(newloaclpath, ftpfile, filter);
            }
            sftp.cd(PARETN_DIR); // 下载完成后返回上级目录
        } else {
            retrieveFile(local, remotefile);
        }
    }

    /**
     * 获取文件远程文件.
     * 
     * @param local
     *            本地文件路径
     * @param filename
     *            远程文件名称
     * @param filter
     *            过滤字符串
     * @throws SftpException
     *             sftp异常
     * @see {@link ChannelSftp#get(String, String)}
     */
    private void retrieveFile(String local, RemoteFile remotefile)
                    throws SftpException {
        FileUtil.createDir(local); // 创建本地文件夹

        String localfile = generateLocalFilename(local, remotefile.getName());
        sftp.get(remotefile.getAbsolutePath(), localfile);
        LOGGER.trace("文件下载完成 ：remote - {}, local - {}",
                        new Object[] {remotefile.getAbsolutePath(), localfile});
    }

    /**
     * 生成本地路径.
     * 
     * <pre>
     * 根据远程服务器的文件路径生成本地路径
     * </pre>
     * 
     * @param local
     *            本地路径
     * @param remotepath
     *            远程路径
     * @return 本地完整路径
     */
    private String generateLocalPath(String local, String filename) {
        return FilenameUtils.getFullPath(local) + File.separator + filename + File.separator;
    }

    /**
     * 生成本地文件名.
     * 
     * <pre>
     * 如果本地文件名中包含文件名，则使用本地文件名，
     * 如果没有文件名则使用远程文件名。
     * </pre>
     * 
     * @param local
     *            本地路径（文件/文件夹）
     * @param filename
     *            远程文件名
     * @return 本地文件绝对路径
     */
    private String generateLocalFilename(String local, String filename) {
        String localpath = FilenameUtils.getFullPath(local);
        String localname = FilenameUtils.getName(local);
        String localFilename = localpath + File.separator
                               + (StringUtils.isEmpty(localname) ? filename : localname);
        return localFilename;
    }


    /**
     * 获取远程文件信息.
     * 
     * @param remote
     *            远程路径
     * @param filter
     *            过滤器
     * @throws SftpException
     *             见ls命令抛出异常说明
     * @see {@link ChannelSftp#ls(String) }
     */
    private List<RemoteFile> listFiles(String remote, String filter)
                    throws SftpException {
        List<RemoteFile> remotefiles = new ArrayList<RemoteFile>();

        @SuppressWarnings("unchecked")
        Vector<LsEntry> files = sftp.ls(remote);
        for (LsEntry entry : files) {
            String name = entry.getFilename();
            boolean isdir = entry.getAttrs().isDir();
            if (!isdir && StringUtils.isNotEmpty(filter) && !name.contains(filter)) {
                continue;
            }
            remotefiles.add(new RemoteFile(remote, name, isdir));
        }
        return remotefiles;
    }
}
